<?php


namespace Wukongke\HiHealth\Client;


use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\ClientInterface as HttpClientInterface;
use GuzzleHttp\Exception\GuzzleException;
use Wukongke\HiHealth\Environment\EnvironmentInterface;
use Wukongke\HiHealth\Environment\Production;

class Client
{
    /**
     * @var string
     */
    private $accessToken;
    /**
     * @var HttpClientInterface
     */
    private $httpClient;
    /**
     * @var EnvironmentInterface|null
     */
    private $environment;

    public function __construct(string $accessToken)
    {
        $this->accessToken = $accessToken;
        $this->environment = new Production();
        $this->httpClient = new HttpClient();
    }

    //============hihealth接口==================
    /**
     * 查询用户每日健康汇总数据
     * @param int $startDate 查询开始日期，格式yyyyyMMdd。
     * @param int $endDate 查询结束日期，格式yyyyyMMdd
     * 如果查询单日数据，则查询开始时间等于查询结束时间。
     * 注：目前最大支持查询100天的数据
     * @param int $type 健康数据类型。目前支持取值为：
     * 7：心率
     * 9：科学睡眠数据
     * @return mixed
     * @throws ClientException
     */
    public function getHealthStat(int $startDate, int $endDate, int $type)
    {
        return $this->request('com.huawei.fit.getHealthStat', [
            'startDate' => $startDate,
            'endDate' => $endDate,
            'type' => $type,
        ]);
    }

    /**
     * 查询用户健康明细数据
     * @param int $startTime 查询开始时间，为UTC时间，精确到毫秒。
     * @param int $endTime 查询结束日期，为UTC时间，精确到毫秒。 注：目前最大支持查询时间间隔为10天的数据。
     * @param int $type 健康数据类型。目前支持取值为：
     * 4：血糖
     * 5：血压
     * 7：心率
     * 8：体重体脂
     * 9：科学睡眠
     * @return mixed
     * @throws ClientException
     */
    public function getHealthData(int $startTime, int $endTime, int $type)
    {
        return $this->request('com.huawei.fit.getHealthData', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'type' => $type,
        ]);
    }

    //===========health kit 接口===========
    //-----------数据采集器接口--------------
    /**
     * 创建数据采集器
     * @param string collectorType 数据采集器类型。可支持的取值如下： raw：原始类型 derived：派生类型
     * @param object appInfo 数据采集器的应用程序信息, 请参见AppInfo数据模型
     * @param object collectorDataType 数据类型, 请参见DataType数据模型
     * @param string [collectorId] 数据采集器唯一标识，该字段由请求中的参数根据规则生成
     * @param string [collectorName] 使用该名称来标识用户下同一类型数据产生的数据采集器，内部标识
     * @param string [name] 用户页面展示的数据采集器名称
     * @param object [deviceInfo] 集成各种传感器的硬件设备描述，例如：手表、手环等可穿戴设备。请参见DeviceInfo数据模型
     */
    public function createDataCollectors(
        string $collectorType,
        object $appInfo,
        object $collectorDataType,
        string $collectorId = null,
        string $collectorName = null,
        string $name = null,
        object $deviceInfo = null
    )
    {
        return $this->healthKitRequest('POST', 'dataCollectors', [
            'collectorType' => $collectorType,
            'appInfo' => $appInfo,
            'collectorDataType' => $collectorDataType,
            'collectorId' => $collectorId,
            'collectorName' => $collectorName,
            'name' => $name,
            'deviceInfo' => $deviceInfo
        ]);
    }
    /**
     * 删除数据采集器
     * @param string dataCollectorId 数据采集器的唯一标识
     */
    public function deleteDataCollectors(string $dataCollectorId)
    {
        return $this->healthKitRequest('DELETE', 'dataCollectors/'.$dataCollectorId, []);
    }
    /**
     * 更新数据采集器
     * @param string dataCollectorId 数据采集器的唯一标识
     * @param string collectorType 数据采集器类型。可支持的取值如下： raw：原始类型 derived：派生类型
     * @param object appInfo 数据采集器的应用程序信息, 请参见AppInfo数据模型
     * @param object collectorDataType 数据类型, 请参见DataType数据模型
     * @param string [collectorId] 数据采集器唯一标识，该字段由请求中的参数根据规则生成
     * @param string [collectorName] 使用该名称来标识用户下同一类型数据产生的数据采集器，内部标识
     * @param string [name] 用户页面展示的数据采集器名称
     * @param object [deviceInfo] 集成各种传感器的硬件设备描述，例如：手表、手环等可穿戴设备。请参见DeviceInfo数据模型
     */
    public function updateDataCollectors(
        string $dataCollectorId,
        string $collectorType,
        object $appInfo,
        object $collectorDataType,
        string $collectorId = null,
        string $collectorName = null,
        string $name = null,
        object $deviceInfo = null
    )
    {
        return $this->healthKitRequest('PUT', 'dataCollectors/'.$dataCollectorId, [
            'collectorType' => $collectorType,
            'appInfo' => $appInfo,
            'collectorDataType' => $collectorDataType,
            'collectorId' => $collectorId,
            'collectorName' => $collectorName,
            'name' => $name,
            'deviceInfo' => $deviceInfo
        ]);
    }
    /**
     * 查询指定数据采集器
     * @param string dataCollectorId 数据采集器的唯一标识
     */
    public function getDataCollectorsData(string $dataCollectorId)
    {
        return $this->healthKitRequest('GET', 'dataCollectors/'.$dataCollectorId, []);
    }
    /**
     * 查询全部数据采集器
     * @param string dataTypeName 数据类型的名称。如果未指定，则将返回所有数据采集器。长度范围：1~300。可使用HUAWEI提供的公共数据类型，也支持自定义数据类型
     * @param string cursor 分页标识。每次查询超过1万条数据返回结果会包含分页标识，可使用此标识进行后续查询。小写字母/数字（长度1~1000）
     */
    public function getAllDataCollectors(string $dataTypeName = null, string $cursor = null)
    {
        return $this->healthKitRequest('GET', 'dataCollectors', [
            'dataTypeName' => $dataTypeName,
            'cursor' => $cursor
        ]);
    }

    //-----------采样数据集接口---------------
    /**
     * 查询用户身高信息
     */
    public function getHealthHeight()
    {
        return $this->healthKitRequest('GET', 'sampleSets/latestSamplePoint', [
            'dataType' => 'com.huawei.instantaneous.height'
        ]);
    }
    /**
     * 查询采样数据的历史记录
     * @param string dataCollectorId 数据采集器的唯一标识
     * @param int limit 单次分页查询长度。取值范围：0~1000
     * @param string 分页标识。填入上一个请求的cursor值进行后续查询, 小写字母/数字（长度1~1000位）
     */
    public function getDataCollectorsSampleSetsHistoryData(string $dataCollectorId, int $limit = null, string $cursor = null)
    {
        return $this->healthKitRequest('GET', 'dataCollectors/'.$dataCollectorId.'/sampleSets/history', [
            'limit' => $limit,
            'cursor' => $cursor
        ]);
    }
    /**
     * 上传数据采集器采集的采样数据
     * @param string dataCollectorId 数据采集器的唯一标识
     * @param string sampleSetId 采样数据集标识符
     * @param int startTime 所有采样点的最小开始时间。单位：纳秒
     * @param int endTime 所有采样点的最大结束时间。单位：纳秒
     * @param array 采样点SamplePoint列表。每次PATCH最多上传1000个SamplePoint, 请参见SamplePoint数据模型
     */
    public function addDataCollectorsSampleSets(
        string $dataCollectorId,
        string $sampleSetId,
        int $startTime,
        int $endTime,
        array $samplePoints
    )
    {
        return $this->healthKitRequest('PATCH', 'dataCollectors/'.$dataCollectorId.'/sampleSets/'.$sampleSetId, [
            'dataCollectorId' => $dataCollectorId,
            'startTime' => $startTime,
            'endTime' => $endTime,
            'samplePoints' => $samplePoints
        ]);
    }
    /**
     * 查询指定时间段内的采样数据集
     * @param string dataCollectorId 数据采集器的唯一标识
     * @param string sampleSetId 采样数据集标识符
     * @param int [limit] 单次分页查询长度。取值范围：[0, 2000]
     * @param string [cursor] 分页标识。填入上一个请求的cursor值进行后续查询, 小写字母/数字（长度1~1000）
     */
    public function getDataCollectorsSampleSetsData(string $dataCollectorId, string $sampleSetId, int $limit = null, string $cursor = null)
    {
        return $this->healthKitRequest('GET', 'dataCollectors/'.$dataCollectorId.'/sampleSets/'.$sampleSetId, [
            'limit' => $limit,
            'cursor' => $cursor
        ]);
    }
    /**
     * 删除指定时间段内的采样数据集
     * @param string dataCollectorId 数据采集器的唯一标识
     * @param string sampleSetId 采样数据集标识符。单位：纳秒
     */
    public function deleteDataCollectorsSampleSets(string $dataCollectorId, string $sampleSetId)
    {
        return $this->healthKitRequest('DELETE', 'dataCollectors/'.$dataCollectorId.'/sampleSets/'.$sampleSetId, []);
    }
    /**
     * 采样数据明细查询
     * @param int [startTime] 期望被聚合的数据开始时间，未配置表示从0开始, 单位：毫秒
     * @param int endTime 期望被聚合的数据结束时间, 单位：毫秒
     * @param array polymerizeWith 聚合数据的标准, 至少提供一个聚合标准, 每个聚合标准产生一个汇总采样数据集。
     */
    public function getSampleSetPolymerizeData(int $startTime = null, int $endTime, array $polymerizeWith)
    {
        return $this->healthKitRequest('POST', 'sampleSet:polymerize', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'polymerizeWith' => $polymerizeWith
        ]);
    }
    /**
     * 将特定数据类型或数据采集器的数据按一定规则进行聚合分组。
     * @param int [startTime] 期望被聚合的数据开始时间，未配置表示从0开始, 单位：毫秒
     * @param int endTime 期望被聚合的数据结束时间, 单位：毫秒
     * @param array polymerizeWith 聚合数据的标准, 至少提供一个聚合标准, 每个聚合标准产生一个汇总采样数据集。
     * @param object [groupByTime] 指定数据由时间间隔聚合数据
     * 
     */
    public function getSampleSetPolymerizeDataStatic(int $startTime = null, int $endTime, array $polymerizeWith, object $groupByTime = null)
    {
        return $this->healthKitRequest('POST', 'sampleSet:polymerize', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'polymerizeWith' => $polymerizeWith,
            'groupByTime' => $groupByTime
        ]);
    }
    /**
     * 采样数据多日统计查询
     * @param string startDay 期望被聚合的数据开始日期, 单位：天
     * @param string endDay 期望被聚合的数据结束日期, 单位：天
     * @param array[string] dataTypes 期望聚合的数据类型, 至少提供一个数据类型, 每个数据类型产生一个汇总采样数据集。
     * @param string timeZone 指定时区
     */
    public function getSampleSetDailyPolymerize(string $startDay, string $endDay, array $dataTypes, string $timeZone)
    {
        return $this->healthKitRequest('POST', 'sampleSet:dailyPolymerize', [
            'startDay' => $startDay,
            'endDay' => $endDay,
            'dataTypes' => $dataTypes,
            'timeZone' => $timeZone
        ]);
    }
    /**
     * 查询用户数据类型最新采样数据
     * @param array[string] $dataType 健康数据类型名称, 最少查询一个，最多查询20个。目前支持取值为：心率、血压、血氧、血糖浓度、体重、身高、压力
     * com.huawei.instantaneous.height：身高
     * com.huawei.instantaneous.body_weight: 体重
     * com.huawei.instantaneous.spo2： 血氧
     * com.huawei.instantaneous.blood_glucose: 血糖
     */
    public function getHealthLatestSamplePoint(array $dataType)
    {
        return $this->healthKitRequest('GET', 'sampleSets/latestSamplePoint', [
            'dataType' => $dataType
        ]);
    }

    //------------运动记录接口---------------
    /**
     * 查询已创建的运动记录
     * @param int startTime 开始时间。13位整数的时间戳，单位：毫秒
     * @param int endTime 结束时间。13位整数的时间戳，单位：毫秒
     * @param array[int] [activityType] 待查询的运动类型列表
     * @param array[string] [detailDataType] 关联数据类型名称
     */
    public function getActivityRecords(int $startTime, int $endTime, array $activityType = null, array $detailDataType = null)
    {
        return $this->healthKitRequest('GET', 'activityRecords', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'activityType' => $activityType,
            'detailDataType' => $detailDataType
        ]);
    }
    /**
     * 删除指定的运动记录
     * @param string activityRecordId 运动记录ID。需与请求体id保持一致，长度范围：1~1000.
     */
    public function deleteActivityRecords(int $activityRecordId)
    {
        return $this->healthKitRequest('DELETE', 'activityRecords/'.$activityRecordId, []);
    }
    /**
     * 删除该用户当前应用的所有运动记录
     * @param string deleteMode 删除数据的模式, 取值只支持App：删除用户某个App的所有数据。
     */
    public function deleteAllActivityRecords(string $deleteMode = 'app')
    {
        return $this->healthKitRequest('DELETE', 'activityRecords', [
            'deleteMode' => $deleteMode
        ]);
    }

    //------------健康记录接口---------------
    /**
     * 查询数据类型的健康记录
     * @param long $startTime 开始时间。单位：纳秒
     * @param long $endTime 结束时间。单位：纳秒
     * @param string $dataType 数据类型名称
     * @param array[string] $subDataType 关联数据类型名称
     */
    public function getHealthRecords(int $startTime, int $endTime, string $dataType, array $subDataType = null)
    {
        return $this->healthKitRequest('GET', 'healthRecords', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'dataType' => $dataType,
            'subDataType' => $subDataType
        ]);
    }

    /**
     * 上传健康记录
     * @param string $dataCollectorId 数据采集器的唯一标识
     * @param array[object] $healthRecords 健康记录HealthRecord列表。每次PATCH最多上传1000个HealthRecord
     */
    public function addHealthRecords(string $dataCollectorId, array $healthRecords)
    {
        return $this->healthKitRequest('PATCH', 'dataCollectors/'.$dataCollectorId.'/healthRecords', [
            'healthRecords' => $healthRecords
        ]);
    }

    /**
     * 删除时间段内的健康记录
     * @param string $dataCollectorId 数据采集器的唯一标识
     * @param long $startTime 	开始时间, 单位：纳秒。
     * @param long $endTime 结束时间, 单位：纳秒
     * @param bool $deelteSubData 删除子数据, true: 删除, false: 不删除
     */
    public function deleteDataCollectorsHealthRecords(string $dataCollectorId, int $startTime, int $endTime, bool $deleteSubData)
    {
        return $this->healthKitRequest('DELETE', 'dataCollectors/'.$dataCollectorId.'/healthRecords', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'deleteSubData' => $deleteSubData
        ]);
    }

   /**
     * 查询时间段内的健康记录
     * @param string $dataCollectorId 数据采集器的唯一标识
     * @param long $startTime 	开始时间, 单位：纳秒。
     * @param long $endTime 结束时间, 单位：纳秒
     * @param bool $limit 单次分页查询长度。取值范围：0~2000
     * @param string $cursor 分页标识。填入上一个请求的cursor值进行后续查询, 小写字母/数字（长度1~1000）
     */
    public function getDataCollectorsHealthRecords(string $dataCollectorId, int $startTime, int $endTime, int $limit = null, string $cursor = null)
    {
        return $this->healthKitRequest('GET', 'dataCollectors/'.$dataCollectorId.'/healthRecords', [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'limit' => $limit,
            'cursor' => $cursor
        ]);
    }


    //------------用户清除数据和取消应用权限接口----------------------
    /**
     * 隐私授权状态查询接口
     */
    public function getUserProfilePrivacyRecords()
    {
        return $this->healthKitRequest('GET', 'profile/privacyRecords', []);
    }
    /**
     * 根据数据类型删除App创建的所有数据采集器及对应的采样数据集。
     * @param string $deleteMode 删除数据的模式, 取值只支持App：删除用户某个App的所有数据。
     * @param string $dataTypeName 按数据类型删除当前App创建的所有数据采集器和采样集。
     */
    public function deleteUserDataCollectors(string $deleteMode, string $dataTypeName)
    {
        return $this->healthKitRequest('DELETE', 'dataCollectors', [
            'deleteMode' => $deleteMode,
            'dataTypeName' => $dataTypeName
        ]);
    }
    /**
     * 查询用户授予某个应用的权限信息
     * @param string $appId 待查询应用Id（三方自身的应用标识
     * @param string $lang 语言描述，比如zh-cn
     */
    public function getUserAppConsents(string $appId, string $lang)
    {
        return $this->healthKitRequest('GET', 'consents/'.$appId, [
            'lang' => $lang
        ]);
    }
    /**
     * 取消授权，取消用户对该应用的全部权限
     * @param string $appId 应用Id（三方自身的应用标识）
     * @param bool [deleteData] 是否立即删除该应用创建的数据，默认场景为false（若用户在三天之内未重新授权，HealthKit会自动删除该应用创建的数据）
     */
    public function delUserAppConsents(string $appId)
    {
        return $this->healthKitRequest('DELETE', 'consents/'.$appId.'?deleteData=false');
    }

    //------------数据订阅接口---------------
    /**
     * 新增/更新订阅记录
     * @param string subscriberId 订阅者ID，订阅者ID可从联盟网站上查询。
     * @param array eventTypes 关注/订阅的事件列表。支持的事件类型请参见订阅事件。
     */
    public function addOrUpdateSubscriptions(string $subscriberId, array $eventTypes)
    {
        return $this->healthKitRequest('POST', 'subscriptions', [
            'subscriberId' => $subscriberId,
            'eventTypes' => $eventTypes,
        ]);
    }
    /**
     * 删除指定的订阅记录。
     * @param string subscriptionId 订阅记录唯一标识。
     */
    public function delSubscriptions(string $subscriptionId)
    {
        return $this->healthKitRequest('DELETE', 'subscriptions/'.$subscriptionId);
    }
    /**
     * 删除指定的订阅记录。
     * @param string eventType 关注/订阅的事件。
     */
    public function delSubscriptionsByWhere(string $eventType)
    {
        return $this->healthKitRequest('DELETE', 'subscriptions?eventType='.$eventType);
    }
    /**
     * 查询订阅记录列表
     */
    public function getSubscriptions()
    {
        return $this->healthKitRequest('GET', 'subscriptions', []);
    }
    /**
     * 查询场景事件结果
     * @param string subscriptionId 订阅记录唯一标识。
     */
    public function getSubscriptionsAchievedRecord(string $subscriptionId)
    {
        return $this->healthKitRequest('GET', 'subscriptions/'.$subscriptionId.'/achievedRecord', []);
    }

    private function healthKitRequest(string $reqMethod = 'GET', string $url = null, array $req = null)
    {
        $headers = [
            'Content-type' => 'application/json; charset=UTF-8',
            'Authorization' => 'Bearer '.$this->accessToken
        ];
        $client = $this->getHttpClient();
        try {
            $reqParams = [
                'headers' => $headers,
            ];
            if ($reqMethod === 'GET' || $reqMethod === 'DELETE') {
                $reqParams['query'] = $req;
            } else {
                $reqParams['json'] = $req;
            }
            $response = $client->request($reqMethod, "{$this->environment->getHealthKitUrl()}{$url}", $reqParams);
        } catch (GuzzleException $e) {
            throw new ClientException('', $e->getCode() ?? 0, $e);
        }

        return \json_decode($response->getBody(), true);

    }

    /**
     * @param string $name
     * @param array|null $req
     * @param string|null $version
     * @param array|null $extra
     * @return mixed
     * @throws ClientException
     */
    private function request(string $name, array $req = null, string $version = null, array $extra = null)
    {
        $params = [
            'access_token' => $this->accessToken,
            'nsp_ts' => \time(),
            'nsp_svc' => $name,
        ];
        if ($extra !== null) {
            $params = \array_merge($extra, $params);
        }
        if ($req !== null) {
            $params['req'] = empty($req) ? '{}' : \json_encode($req, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }
        if ($version !== null) {
            $params['nsp_ver'] = $version;
        }

        $client = $this->getHttpClient();
        try {
            $response = $client->request('POST', $this->environment->getHealthUrl(), [
                'form_params' => $params
            ]);
        } catch (GuzzleException $e) {
            throw new ClientException('Request to Huawei cloud failed', $e->getCode() ?? 0, $e);
        }

        return \json_decode($response->getBody(), true);
    }

    /**
     * @return HttpClientInterface
     */
    private function getHttpClient(): HttpClientInterface
    {
        return $this->httpClient;
    }

    /**
     * @param HttpClientInterface $httpClient
     * @return Client
     */
    public function setHttpClient(HttpClientInterface $httpClient): Client
    {
        $this->httpClient = $httpClient;
        return $this;
    }

    /**
     * @return EnvironmentInterface|null
     */
    public function getEnvironment(): ?EnvironmentInterface
    {
        return $this->environment;
    }

    /**
     * @param EnvironmentInterface|null $environment
     * @return Client
     */
    public function setEnvironment(?EnvironmentInterface $environment): Client
    {
        $this->environment = $environment;
        return $this;
    }

    /**
     * @return string
     */
    public function getAccessToken(): string
    {
        return $this->accessToken;
    }

    /**
     * @param string $accessToken
     * @return Client
     */
    public function setAccessToken(string $accessToken): Client
    {
        $this->accessToken = $accessToken;
        return $this;
    }
}